﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Serialization;

namespace Microsoft.Mtool.FSM
{
    public class SuiteMaker
    {
        private List<TokenImpl> tokens;
        private List<FunctionImpl> funcs;
        private List<List<Tour>> cases;
        private List<Dictionary<string, string>> varmaps;

        public SuiteMaker()
        {
            tokens = new List<TokenImpl>();
            funcs = new List<FunctionImpl>();
            cases = new List<List<Tour>>();
            varmaps = new List<Dictionary<string, string>>();
        }

        public static List<List<Tour>> MakeSuite(FSM machine, int minStep, int maxStep, int maxRepeat)
        {
            Tour.ResetIdCounter();
            List<List<Tour>> retv = new List<List<Tour>>();
            foreach (State s in machine.States)
            {
                if (s.Type == StateType.Entry)
                {
                    retv.Add(machine.GenerateTours(s, minStep, maxStep, maxRepeat));
                }
            }
            return retv;
        }

        public void AppendToken(TokenImpl t)
        {
            tokens.Add(t);
        }

        public void AppendFunction(FunctionImpl method)
        {
            funcs.Add(method);
        }

        public void AppendCaseGroup(List<Tour> group)
        {
            cases.Add(group);
        }

        public void AppendVarMap(Dictionary<string, string> map)
        {
            varmaps.Add(map);
        }

        public void Save(string filename)
        {
            StreamWriter writer = new StreamWriter(filename, false);

            int pos1 = filename.LastIndexOf('\\');
            if (pos1 == -1) throw new Exception("Invalid filename");

            int pos2 = filename.LastIndexOf('.');
            if (pos2 == -1 || pos2 < pos1) throw new Exception("Invalid filename");

            writer.WriteLine("using System;");
            writer.WriteLine(); 
            writer.WriteLine("namespace Microsoft.Mtool.GenSuites");
            writer.WriteLine("{");            
            writer.WriteLine("\tpublic class " + filename.Substring(pos1+1, pos2-pos1-1));
            writer.WriteLine("\t{");
            SaveTokens(writer);
            SaveFunctions(writer);
            SaveVars(writer);
            SaveDefault(writer);
            SaveCaseGroups(writer);

            SaveMain(writer);
            writer.WriteLine("\t}");
            writer.WriteLine("}");
            
            writer.Flush();
            writer.Close();
        }

        private void SaveImplHeader(StreamWriter writer, ActionImpl impl, bool isDef)
        {
            SaveImplHeader(writer, impl, isDef, 3);
        }

        private void SaveImplHeader(StreamWriter writer, ActionImpl impl, bool isDef, int indent)
        {
            string type = isDef ? "object " : "";
            if (isDef)
                writer.Write("\t\tprivate static void " + impl.Name + "(");
            else if (indent == 3)
                writer.Write("\t\t\t" + impl.Name + "(");
            else if (indent == 4)
                writer.Write("\t\t\t\t" + impl.Name + "(");

            bool flag = false;
            if (impl.InParas != null && impl.InParas.Count != 0)
            {
                flag = true;
                writer.Write(type + (isDef ? impl.InParas[0].Name : impl.InParas[0].Var.Name));
                for (int i = 1; i < impl.InParas.Count; i++)
                    writer.Write(", " + type + (isDef ? impl.InParas[i].Name : impl.InParas[i].Var.Name));
            }
            if (impl.OutParas != null && impl.OutParas.Count != 0)
            {
                writer.Write((flag ? ", out " : "out ") + type + (isDef ? impl.OutParas[0].Name : impl.OutParas[0].Var.Name));
                for (int i = 1; i < impl.OutParas.Count; i++)
                    writer.Write(", out " + type + (isDef ? impl.OutParas[i].Name : impl.OutParas[i].Var.Name));
            }
            writer.WriteLine(isDef ? ")" : ");");
        }

        private void SaveTokens(StreamWriter writer)
        {
            writer.WriteLine("\t\t#region TOKENS");
            writer.WriteLine();
            foreach (TokenImpl t in tokens)
            {
                SaveImplHeader(writer, t, true);
                writer.WriteLine("\t\t{");
                writer.WriteLine("\t\t\tthrow new NotImplementedException();");
                writer.WriteLine("\t\t}");
                writer.WriteLine();
            }
            writer.WriteLine("\t\t#endregion");
            writer.WriteLine();
        }

        private void SaveFunctions(StreamWriter writer)
        {
            writer.WriteLine("\t\t#region FUNCTIONS");
            writer.WriteLine();
            foreach (FunctionImpl f in funcs)
            {
                SaveImplHeader(writer, f, true);
                writer.WriteLine("\t\t{");
                foreach (ActionImpl impl in f.InnerAction.Stubs)
                {
                    SaveImplHeader(writer, impl, false);
                }
                writer.WriteLine("\t\t}");
                writer.WriteLine();
            }
            writer.WriteLine("\t\t#endregion");
            writer.WriteLine();
        }

        private void SaveVars(StreamWriter writer)
        {
            writer.WriteLine("\t\t#region VARIABLES");
            writer.WriteLine();
            foreach (string varName in varmaps[0].Keys)
            {
                writer.WriteLine("\t\tprivate static object " + varName + ";");
            }
            writer.WriteLine();
            writer.WriteLine("\t\t#endregion");
            writer.WriteLine();
        }

        private void SaveDefault(StreamWriter writer)
        {
            writer.WriteLine("\t\t#region DEFAULT");
            writer.WriteLine();
            
            writer.WriteLine("\t\tprivate static int errNum;");
            writer.WriteLine("\t\tprivate static int allNum;");
            
            writer.WriteLine();
            writer.WriteLine("\t\t#endregion");
            writer.WriteLine();
        }

        private List<string> caseNameList = new List<string>();

        private void SaveCaseGroups(StreamWriter writer)
        {
            caseNameList.Clear();
            writer.WriteLine("\t\t#region TEST CASES");
            writer.WriteLine();
            for (int i = 0; i < cases.Count; i++)
            {
                for (int j = 0; j < cases[i].Count; j++)
                {
                    caseNameList.Add(string.Format("TestCase_{0}_{1}", i, j));
                    writer.WriteLine(string.Format("\t\tprivate static void TestCase_{0}_{1}()", i, j));
                    writer.WriteLine("\t\t{");
                    writer.WriteLine("\t\t\ttry");
                    writer.WriteLine("\t\t\t{");
                    foreach (Action act in cases[i][j].Actions)
                    {
                        foreach(ActionImpl impl in act.Stubs)
                        {
                            SaveImplHeader(writer, impl, false, 4);
                        }
                    }
                    writer.WriteLine();
                    writer.WriteLine("\t\t\t\tConsole.WriteLine(\"TestCase_{0}_{1} is OK.\");", i, j);
                    writer.WriteLine("\t\t\t}");
                    writer.WriteLine("\t\t\tcatch(Exception e)");
                    writer.WriteLine("\t\t\t{");
                    writer.WriteLine("\t\t\t\terrNum++;");
                    writer.WriteLine("\t\t\t\tConsole.WriteLine(\"TestCase_{0}_{1} is failed.\");", i, j);
                    writer.WriteLine("\t\t\t}");
                    writer.WriteLine("\t\t\tfinally");
                    writer.WriteLine("\t\t\t{");
                    writer.WriteLine("\t\t\t\tallNum++;");
                    writer.WriteLine("\t\t\t}");
                    writer.WriteLine("\t\t}");
                    writer.WriteLine();
                }                
            }
            writer.WriteLine("\t\t#endregion");
            writer.WriteLine();
        }

        private void SaveCurVarMap(StreamWriter writer, Dictionary<string, string> vmap)
        {
            foreach(string vn in vmap.Keys)
            {
                writer.WriteLine("\t\t\t{0} = {1};", vn, vmap[vn]);
            }
        }

        private void SaveMain(StreamWriter writer)
        {
            writer.WriteLine("\t\tstatic void Main(string[] args)");
            writer.WriteLine("\t\t{");
            // set para and run each test case
            int i = 0;
            bool isFirst = true;
            writer.WriteLine("\t\t\terrNum = 0; allNum = 0;");
            writer.WriteLine();
            foreach (Dictionary<string, string> vm in varmaps)
            {
                if (isFirst)
                    isFirst = false;
                else
                    writer.WriteLine();

                writer.WriteLine("\t\t\tConsole.WriteLine(\"# Case {0}\");", i++);
                foreach (string name in caseNameList)
                {                   
                    SaveCurVarMap(writer, vm);
                    writer.WriteLine("\t\t\t{0}();", name);
                }                
            }
            writer.WriteLine();
            writer.WriteLine("\t\t\tConsole.WriteLine();");
            
            writer.WriteLine("\t\t\tConsole.WriteLine(\"Finish Test:\");"); 
            writer.Write("\t\t\tConsole.WriteLine(\"pass = {0}, error = {1}, total = {2}, passrate = {3}%.\", ");
            writer.WriteLine("allNum-errNum, errNum, allNum, (allNum == 0) ? 0 : 100-Convert.ToInt32(((errNum+0.0)/allNum)*100) );");

            writer.WriteLine("\t\t\tConsole.WriteLine();");
            writer.WriteLine("\t\t\tConsole.WriteLine(\"Press any key to exit...\");");
            writer.WriteLine("\t\t\tConsole.ReadLine();");
            writer.WriteLine("\t\t}");
        }
    }
}
